//-----------------------------------------------------------------
//  Copyright 2010 Brady Wright and Above and Beyond Software
//	All rights reserved
//-----------------------------------------------------------------


using UnityEngine;
using System.Collections;



// AutoSprite-Control-State/Element Info struct:
public class ASCSEInfo
{
	// Reference to the texture to be 
	// used for this state/element:
	public Texture2D tex;

	// Reference to the SpriteState
	// object:
	public TextureAnim stateObj;

	// Transition
	public EZTransitionList transitions;

	// State text
	public string stateLabel;
}



/// <remarks>
/// Serves as the base for all AutoSprite-based controls.
/// The main purpose of having this is so we can deal with
/// all control types as a group, such as using a custom
/// inspector.
/// </remarks>
public abstract class AutoSpriteControlBase : AutoSpriteBase, IControl, IUIObject, IPackableControl
{
	//---------------------------------------------------
	// State tracking stuff
	//---------------------------------------------------

	// Will help us pick a useful camera at startup
	// if none is already set.
	protected bool nullCamera = false;

	//---------------------------------------------------
	// Control property stuff
	//---------------------------------------------------

	/// <summary>
	/// String whose meaning is to keep the previous
	/// state's label.
	/// </summary>
	public const string DittoString = "[\"]";

	/// <summary>
	/// Text to be displayed on the control.
	/// Do not set this directly in-code. Instead, use
	/// the "Text" property or else your changes will
	/// not take effect.
	/// </summary>
	public string text;

	/// <summary>
	/// Reference to optional SpriteText which will display
	/// this item's text. It is STRONGLY recommended that
	/// this mesh exist on a GameObject that is a child of
	/// the list item itself.
	/// </summary>
	public SpriteText spriteText;			// Mesh that will display our text

	/// <summary>
	/// When text is generated by the control at runtime, 
	/// it will, by default, have its offsetZ setting set 
	/// to this value.  NOTE: Negative values will result
	/// in text being in front of the control.  Positive
	/// values will place the text behind the control.
	/// </summary>
	public float textOffsetZ = -0.1f;

	/// <summary>
	/// When true, of a collider is generated for the
	/// control, the associated text is taken into
	/// account when calculating the extents of the
	/// collider.
	/// </summary>
	public bool includeTextInAutoCollider = true;

	// Default text options, used when creating a new child
	// SpriteText object:
	protected SpriteText.Anchor_Pos defaultTextAnchor = SpriteText.Anchor_Pos.Middle_Center;
	protected SpriteText.Alignment_Type defaultTextAlignment = SpriteText.Alignment_Type.Center;

	/// <summary>
	/// Sets the text to be displayed in this control.
	/// </summary>
	public virtual string Text
	{
		get { return text; }

		set
		{
			text = value;

			// See if we need to create a TextMesh and an
			// object to host it:
			if(spriteText == null)
			{
				if (text == "")
					return;

				if(UIManager.instance == null)
				{
					Debug.LogWarning("Warning: No UIManager exists in the scene. A UIManager with a default font is required to automatically add text to a control.");
					return;
				}
				else if(UIManager.instance.defaultFont == null)
				{
					Debug.LogWarning("Warning: No default font defined.  A UIManager object with a default font is required to automatically add text to a control.");
					return;
				}

				// Create a GO to host the TextMesh:
				GameObject go = new GameObject();
				go.layer = gameObject.layer;
				go.transform.parent = transform;
				go.transform.localPosition = Vector3.zero;
				go.transform.localRotation = Quaternion.identity;
				go.name = "control_text";

				// Add a mesh renderer:
				MeshRenderer mr = (MeshRenderer) go.AddComponent(typeof(MeshRenderer));
				mr.material = UIManager.instance.defaultFontMaterial;

				// Add the SpriteText component:
				spriteText = (SpriteText)go.AddComponent(typeof(SpriteText));
				spriteText.font = UIManager.instance.defaultFont;

				spriteText.offsetZ = textOffsetZ;

				// Make it persistent or not:
				spriteText.Persistent = persistent;

				// Tell the text object we're its parent
				spriteText.Parent = this;

				// Anchor and align centered by default:
				spriteText.anchor = defaultTextAnchor;
				spriteText.alignment = defaultTextAlignment;
				spriteText.pixelPerfect = true;
				spriteText.SetCamera(renderCamera);

				// Copy over our persistent state if this
				// is runtime, otherwise, just let this
				// get set in Start():
				if(Application.isPlaying)
					spriteText.Persistent = persistent;

				// Make sure it is fully initialized before we assign text:
				spriteText.Start();
			}

			spriteText.Text = text;

			// Assign the text back, in case the spriteText removed
			// some unsupported characters:
			text = spriteText.Text;

			// Update the collider if need be:
			if (includeTextInAutoCollider)
				UpdateCollider();
		}
	}

	/// <summary>
	/// When set to true, the control will instruct any
	/// pointers which have it as their target to
	/// de-target them.  Use this if you are deactivating
	/// a control and want no input to go to it.
	/// It is strongly recommended NOT to use this feature
	/// on any control that appears in a scroll list, or
	/// else you may be unable to scroll past the edge of
	/// the list's viewable area.
	/// </summary>
	public bool detargetOnDisable = false;

	// Tracks whether we are using a pre-made collider.
	protected bool customCollider;

	// The saved size of our box collider (if any)
	protected Vector3 savedColliderSize;

	// The outer edges of the control, including
	// layers and text:
	protected Vector2 topLeftEdge;
	protected Vector2 bottomRightEdge;

	/// <summary>
	/// Can hold a reference to any data that the
	/// developer wishes to be associated with
	/// the control.
	/// </summary>
	[HideInInspector]
	public object data;

	/// <summary>
	/// Holds "boxed" data for the control.
	/// This can be used to associate any
	/// object or value with the control
	/// for later reference and use.
	/// </summary>
	public object Data
	{
		get { return data; }
		set { data = value; }
	}

	// Array of all layer arrays for this control.
	protected SpriteRoot[][] aggregateLayers;

	protected override void Init()
	{
		nullCamera = (renderCamera == null);
		base.Init();
	}

	/// <summary>
	/// Determines whether any associated text
	/// should be taken into account when generating
	/// a collider for the control automatically.
	/// </summary>
	public virtual bool IncludeTextInAutoCollider
	{
		get { return includeTextInAutoCollider; }
		set
		{
			includeTextInAutoCollider = value;
			UpdateCollider();
		}
	}

	public override void Start()
	{
		base.Start();

		if(UIManager.Exists())
		{
			// Choose the first UI Camera by default:
			if (nullCamera && UIManager.instance.uiCameras.Length > 0)
				SetCamera(UIManager.instance.uiCameras[0].camera);

			if(Application.isPlaying)
			{
				if (cancelDragEasing == EZAnimation.EASING_TYPE.Default)
					cancelDragEasing = UIManager.instance.cancelDragEasing;

				if (cancelDragDuration == -1f)
					cancelDragDuration = UIManager.instance.cancelDragDuration;

				if (float.IsNaN(dragOffset))
					dragOffset = UIManager.instance.defaultDragOffset;
			}
		}

		// Make any associated text persistent if we are:
		if (spriteText != null)
		{
			spriteText.Persistent = persistent;
			spriteText.Parent = this;
		}
	}

	// Truncate layers:
	public override void TruncateTop(float pct)
	{
		base.TruncateTop(pct);

		if(aggregateLayers != null)
		{
			for (int i = 0; i < aggregateLayers.Length; ++i)
			{
				if(aggregateLayers[i] != null)
				{
					for (int j = 0; j < aggregateLayers[i].Length; ++j)
						aggregateLayers[i][j].TruncateTop(pct);
				}
			}
		}
	}

	public override void TruncateBottom(float pct)
	{
		base.TruncateBottom(pct);

		if (aggregateLayers != null)
		{
			for (int i = 0; i < aggregateLayers.Length; ++i)
			{
				if (aggregateLayers[i] != null)
				{
					for (int j = 0; j < aggregateLayers[i].Length; ++j)
						aggregateLayers[i][j].TruncateBottom(pct);
				}
			}
		}
	}

	public override void TruncateLeft(float pct)
	{
		base.TruncateLeft(pct);

		if (aggregateLayers != null)
		{
			for (int i = 0; i < aggregateLayers.Length; ++i)
			{
				if (aggregateLayers[i] != null)
				{
					for (int j = 0; j < aggregateLayers[i].Length; ++j)
						aggregateLayers[i][j].TruncateLeft(pct);
				}
			}
		}
	}

	public override void TruncateRight(float pct)
	{
		base.TruncateRight(pct);

		if (aggregateLayers != null)
		{
			for (int i = 0; i < aggregateLayers.Length; ++i)
			{
				if (aggregateLayers[i] != null)
				{
					for (int j = 0; j < aggregateLayers[i].Length; ++j)
						aggregateLayers[i][j].TruncateRight(pct);
				}
			}
		}
	}

	public override void Untruncate()
	{
		base.Untruncate();

		if (aggregateLayers != null)
		{
			for (int i = 0; i < aggregateLayers.Length; ++i)
			{
				if (aggregateLayers[i] != null)
				{
					for (int j = 0; j < aggregateLayers[i].Length; ++j)
						aggregateLayers[i][j].Untruncate();
				}
			}
		}
	}

	public override void Unclip()
	{
		if (ignoreClipping)
			return;

		base.Unclip();

		if (spriteText != null)
			spriteText.Unclip();

		if (aggregateLayers != null)
		{
			for (int i = 0; i < aggregateLayers.Length; ++i)
			{
				if (aggregateLayers[i] != null)
				{
					for (int j = 0; j < aggregateLayers[i].Length; ++j)
						aggregateLayers[i][j].Unclip();
				}
			}
		}

		UpdateCollider();
	}

	public override bool Clipped
	{
		get
		{
			return base.Clipped;
		}
		set
		{
			if (ignoreClipping)
				return;

			base.Clipped = value;

			if (spriteText != null)
				spriteText.Clipped = value;

			if (aggregateLayers != null)
			{
				for (int i = 0; i < aggregateLayers.Length; ++i)
				{
					if (aggregateLayers[i] != null)
					{
						for (int j = 0; j < aggregateLayers[i].Length; ++j)
							aggregateLayers[i][j].Clipped = value;
					}
				}
			}

			UpdateCollider();
		}
	}

	public override Rect3D ClippingRect
	{
		get
		{
			return base.ClippingRect;
		}
		set
		{
			if (ignoreClipping)
				return;

			base.ClippingRect = value;

			if (spriteText != null)
				spriteText.ClippingRect = value;

			if (aggregateLayers != null)
			{
				for (int i = 0; i < aggregateLayers.Length; ++i)
				{
					if (aggregateLayers[i] != null)
					{
						for (int j = 0; j < aggregateLayers[i].Length; ++j)
							aggregateLayers[i][j].ClippingRect = value;
					}
				}
			}

			UpdateCollider();
		}
	}


	public override Camera RenderCamera
	{
		get { return base.RenderCamera; }
		set
		{
			base.RenderCamera = value;

			if (spriteText != null)
				spriteText.RenderCamera = value;
		}
	}


	public override void SetCamera(Camera c)
	{
		base.SetCamera(c);

		if (spriteText != null)
			spriteText.SetCamera(c);
		
		if (pixelPerfect)
			UpdateCollider();
	}


	// Hide layers:
	public override void Hide(bool tf)
	{
		if (!m_started)
			Start();

		// If we're hiding and not already hideAtStart,
		// save the current collider size and set
		// it to zero.
		if(!IsHidden() && tf)
		{
			if(collider is BoxCollider && Application.isPlaying)
			{
				savedColliderSize = ((BoxCollider)collider).size;
				((BoxCollider)collider).size = Vector3.zero;
			}
		}
		else if(IsHidden() && !tf)
		{
			// Else if we're unhiding, restore our collider's size:
			if(collider is BoxCollider)
			{
				((BoxCollider)collider).size = savedColliderSize;
			}
		}

		base.Hide(tf);

		// Hide other layers:
		if (aggregateLayers != null)
		{
			for (int i = 0; i < aggregateLayers.Length; ++i)
			{
				if (aggregateLayers[i] != null)
				{
					for (int j = 0; j < aggregateLayers[i].Length; ++j)
						aggregateLayers[i][j].Hide(tf);
				}
			}
		}

		// Show/Hide text object:
		if(spriteText != null)
		{
			spriteText.Hide(tf);
		}

		// Recalculate our collider:
		if (!tf)
			UpdateCollider();
	}


	public void Copy(IControl c)
	{
		if (!(c is AutoSpriteControlBase))
			return;

		Copy((SpriteRoot)c);
	}

	public void Copy(IControl c, ControlCopyFlags flags)
	{
		if (!(c is AutoSpriteControlBase))
			return;

		Copy((SpriteRoot)c, flags);
	}


	/// <summary>
	/// Copies all of the specified control's settings
	/// to this control, provided they are of the same
	/// type.  One exception is that layers are not
	/// copied as this would require a new allocation
	/// and could negatively impact performance at
	/// runtime.
	/// </summary>
	/// <param name="s">Reference to the control whose settings are to be copied to this control.</param>
	public override void Copy(SpriteRoot s)
	{
		Copy(s, ControlCopyFlags.All);
	}

	/// <summary>
	/// Copies all of the specified control's settings
	/// to this control, provided they are of the same
	/// type.  One exception is that layers are not
	/// copied as this would require a new allocation
	/// and could negatively impact performance at
	/// runtime.
	/// </summary>
	/// <param name="s">Reference to the control whose settings are to be copied to this control.</param>
	public virtual void Copy(SpriteRoot s, ControlCopyFlags flags)
	{
		if( (flags & ControlCopyFlags.Appearance) == ControlCopyFlags.Appearance)
		{
			if (Application.isPlaying && s.Started)
				base.Copy(s);
			else // If we're in-editor, copy the TextureAnims too
				base.CopyAll(s);

			if (!(s is AutoSpriteControlBase))
			{
				if (autoResize || pixelPerfect)
					CalcSize();
				else
					SetSize(s.width, s.height);

				SetBleedCompensation();

				return;
			}
		}



		AutoSpriteControlBase c = (AutoSpriteControlBase)s;

		// Copy transitions:
		if ((flags & ControlCopyFlags.Transitions) == ControlCopyFlags.Transitions)
		{
			if (c is UIStateToggleBtn || !Application.isPlaying)
			{
				if (c.Transitions != null)
				{
					Transitions = new EZTransitionList[c.Transitions.Length];
					for (int i = 0; i < Transitions.Length; ++i)
					{
						Transitions[i] = new EZTransitionList();
						c.Transitions[i].CopyToNew(Transitions[i], true);
					}
				}
			}
			else
			{
				if (Transitions != null && c.Transitions != null)
					for (int i = 0; i < Transitions.Length && i < c.Transitions.Length; ++i)
						c.Transitions[i].CopyTo(Transitions[i], true);
			}
		}


		if ((flags & ControlCopyFlags.Text) == ControlCopyFlags.Text)
		{
			// See if we want to clone the other
			// control's text mesh:
			if (spriteText == null && c.spriteText != null)
			{
				GameObject newText = (GameObject)Instantiate(c.spriteText.gameObject);
				newText.transform.parent = transform;
				newText.transform.localPosition = c.spriteText.transform.localPosition;
				newText.transform.localScale = c.spriteText.transform.localScale;
				newText.transform.localRotation = c.spriteText.transform.localRotation;
			}

			if(spriteText != null)
				spriteText.Copy(c.spriteText);

			text = c.text;
			textOffsetZ = c.textOffsetZ;
			includeTextInAutoCollider = c.includeTextInAutoCollider;
		}

		if ((flags & ControlCopyFlags.Data) == ControlCopyFlags.Data)
		{
			data = c.data;
		}

		if ((flags & ControlCopyFlags.Appearance) == ControlCopyFlags.Appearance)
		{
			// See if we can copy the other control's collider's settings:
			if (c.collider != null)
			{
				if (collider.GetType() == c.collider.GetType())
				{
					if (c.collider is BoxCollider)
					{
						if (collider == null)
							gameObject.AddComponent(typeof(BoxCollider));

						BoxCollider bc1 = (BoxCollider)collider;
						BoxCollider bc2 = (BoxCollider)c.collider;
						bc1.center = bc2.center;
						bc1.size = bc2.size;
					}
					else if (c.collider is SphereCollider)
					{
						if (collider == null)
							gameObject.AddComponent(typeof(SphereCollider));

						SphereCollider sc1 = (SphereCollider)collider;
						SphereCollider sc2 = (SphereCollider)c.collider;
						sc1.center = sc2.center;
						sc1.radius = sc2.radius;
					}
					else if (c.collider is MeshCollider)
					{
						if (collider == null)
							gameObject.AddComponent(typeof(MeshCollider));

						MeshCollider mc1 = (MeshCollider)collider;
						MeshCollider mc2 = (MeshCollider)c.collider;
						mc1.smoothSphereCollisions = mc2.smoothSphereCollisions;
						mc1.convex = mc2.convex;
						mc1.sharedMesh = mc2.sharedMesh;
					}
					else if (c.collider is CapsuleCollider)
					{
						if (collider == null)
							gameObject.AddComponent(typeof(CapsuleCollider));

						CapsuleCollider cc1 = (CapsuleCollider)collider;
						CapsuleCollider cc2 = (CapsuleCollider)c.collider;
						cc1.center = cc2.center;
						cc1.radius = cc2.radius;
						cc1.height = cc2.height;
						cc1.direction = cc2.direction;
					}

					if (collider != null)
						collider.isTrigger = c.collider.isTrigger;
				}
			}
			else if(Application.isPlaying) // Don't create a collider if we're in edit mode
			{
				// Create a default box collider to fit so
				// long as the control isn't of 0 size:
				if (collider == null &&
					width != 0 && height != 0 &&
					!float.IsNaN(width) && !float.IsNaN(height))
				{
					BoxCollider bc = (BoxCollider)gameObject.AddComponent(typeof(BoxCollider));
					bc.size = new Vector3(c.width, c.height, 0.001f);
					bc.center = c.GetCenterPoint();
					bc.isTrigger = true;
				}
				else
				{
					if (collider is BoxCollider)
					{
						BoxCollider bc = (BoxCollider)collider;
						bc.size = new Vector3(c.width, c.height, 0.001f);
						bc.center = c.GetCenterPoint();
					}
					else if (collider is SphereCollider)
					{
						SphereCollider sc = (SphereCollider)collider;
						sc.radius = Mathf.Max(c.width, c.height);
						sc.center = c.GetCenterPoint();
					}
					// Else Fuhgettaboutit
				}
			}
		}

		if ((flags & ControlCopyFlags.DragDrop) == ControlCopyFlags.DragDrop)
		{
			isDraggable = c.isDraggable;
			dragOffset = c.dragOffset;
			cancelDragEasing = c.cancelDragEasing;
			cancelDragDuration = c.cancelDragDuration;
		}

		if ((flags & ControlCopyFlags.Settings) == ControlCopyFlags.Settings)
		{
			detargetOnDisable = c.detargetOnDisable;
		}

		if ((flags & ControlCopyFlags.Invocation) == ControlCopyFlags.Invocation)
		{
			changeDelegate = c.changeDelegate;
			inputDelegate = c.inputDelegate;
		}

		if ((flags & ControlCopyFlags.State) == ControlCopyFlags.State ||
			(flags & ControlCopyFlags.Appearance) == ControlCopyFlags.Appearance)
		{
			Container = c.Container;

			if (Application.isPlaying)
			{
				controlIsEnabled = c.controlIsEnabled;
				Hide(c.IsHidden());
			}

			if (curAnim != null)
			{
				if (curAnim.index == -1)
				{
					if(c.curAnim != null)
						curAnim = c.curAnim.Clone();
					PlayAnim(curAnim);

				}
				else
					SetState(curAnim.index);
			}
			else
				SetState(0);
		}
/*
		if (autoResize || pixelPerfect)
			CalcSize();
		else
			SetSize(s.width, s.height);
*/
	}


	//---------------------------------------------------
	// Boilerplate stuff
	//---------------------------------------------------
	protected override void Awake()
	{
		base.Awake();

		if (dragDropHelper == null)
			dragDropHelper = new EZDragDropHelper(this);
		else
			dragDropHelper.host = this;

		if (collider != null)
			customCollider = true;

		Init();

		AddSpriteResizedDelegate(OnResize);
	}


	protected override void OnEnable()
	{
		base.OnEnable();

		// Since we hide a managed sprite by setting its
		// vertices to 0,0,0, since we just re-enabled
		// they will have been set to something else. So
		// we need to re-set them by hiding again:
		if (managed && m_spriteMesh != null && m_hidden)
			m_spriteMesh.Hide(true);
	}

	protected override void OnDisable()
	{
		base.OnDisable();

		if(Application.isPlaying)
		{
			if (EZAnimator.Exists())
			{
				EZAnimator.instance.Stop(gameObject);
				EZAnimator.instance.Stop(this);
			}

			if(detargetOnDisable && UIManager.Exists())
			{
				UIManager.instance.Detarget(this);
			}
		}
	}

	// Called whenever the control changes size
	protected void OnResize(float newWidth, float newHeight, SpriteRoot sprite)
	{
		UpdateCollider();
	}


	// Adds a collider to this control based upon
	// its outline.
	protected virtual void AddCollider()
	{
		// Don't create our own if a custom collider exists,
		// if we're hidden, or if this is edit-mode
		if (customCollider || !Application.isPlaying || !m_started)
			return;

		BoxCollider bc = (BoxCollider) gameObject.AddComponent(typeof(BoxCollider));
		bc.isTrigger = true;

		if (IsHidden())
		{
			bc.size = Vector3.zero;
		}
		else
			UpdateCollider();
	}


	/// <summary>
	/// Updates the collider of the control so that
	/// it encompasses the extents of the control's
	/// content.
	/// NOTE: To include the control's associated
	/// text in the calculation, be sure to check
	/// the includeTextInAutoCollider box.
	/// </summary>
	public virtual void UpdateCollider()
	{
		if (deleted || m_spriteMesh == null)
			return;

		if (!(collider is BoxCollider) || IsHidden() || m_spriteMesh == null || customCollider)
			return;

		Vector3[] verts = m_spriteMesh.vertices;
		Vector3 min = verts[1];
		Vector3 max = verts[3];

		if (includeTextInAutoCollider && spriteText != null)
		{
			Matrix4x4 sm = spriteText.transform.localToWorldMatrix;
			Matrix4x4 lm = transform.worldToLocalMatrix;
			Vector3 tl = lm.MultiplyPoint3x4(sm.MultiplyPoint3x4(spriteText.TopLeft));
			Vector3 br = lm.MultiplyPoint3x4(sm.MultiplyPoint3x4(spriteText.BottomRight));
			
			if(br.x - tl.x > 0 && tl.y - br.y > 0)
			{
				min.x = Mathf.Min(min.x, tl.x);
				min.y = Mathf.Min(min.y, br.y);
				max.x = Mathf.Max(max.x, br.x);
				max.y = Mathf.Max(max.y, tl.y);
			}
		}

		BoxCollider bc = (BoxCollider)collider;
		bc.size = max - min;
		bc.center = min + bc.size * 0.5f;
		bc.isTrigger = true;
	}

	// Finds the outermost edges of the control,
	// taking all layers into account.
	public virtual void FindOuterEdges()
	{
		if (deleted)
			return;

		// If we're being asked for our outline, then
		// we need to have already started:
		if (!m_started)
			Start();

		topLeftEdge = unclippedTopLeft;
		bottomRightEdge = unclippedBottomRight;

		Matrix4x4 sm;
		Matrix4x4 lm = transform.worldToLocalMatrix;
		Vector3 tl, br;

		if (spriteText != null)
		{
			sm = spriteText.transform.localToWorldMatrix;
			tl = lm.MultiplyPoint3x4(sm.MultiplyPoint3x4(spriteText.UnclippedTopLeft));
			br = lm.MultiplyPoint3x4(sm.MultiplyPoint3x4(spriteText.UnclippedBottomRight));
			topLeftEdge.x = Mathf.Min(topLeftEdge.x, tl.x);
			topLeftEdge.y = Mathf.Max(topLeftEdge.y, tl.y);
			bottomRightEdge.x = Mathf.Max(bottomRightEdge.x, br.x);
			bottomRightEdge.y = Mathf.Min(bottomRightEdge.y, br.y);
		}

		if(aggregateLayers != null)
		{
			for (int i = 0; i < aggregateLayers.Length; ++i)
			{
				for (int j = 0; j < aggregateLayers[i].Length; ++j)
				{
					if (aggregateLayers[i][j].IsHidden() || !aggregateLayers[i][j].gameObject.active)
						continue;

					sm = aggregateLayers[i][j].transform.localToWorldMatrix;
					tl = lm.MultiplyPoint3x4(sm.MultiplyPoint3x4(aggregateLayers[i][j].UnclippedTopLeft));
					br = lm.MultiplyPoint3x4(sm.MultiplyPoint3x4(aggregateLayers[i][j].UnclippedBottomRight));
					topLeftEdge.x = Mathf.Min(topLeftEdge.x, tl.x);
					topLeftEdge.y = Mathf.Max(topLeftEdge.y, tl.y);
					bottomRightEdge.x = Mathf.Max(bottomRightEdge.x, br.x);
					bottomRightEdge.y = Mathf.Min(bottomRightEdge.y, br.y);
				}
			}
		}
	}

	/// <summary>
	/// The top-left edge of the control, when
	/// no clipping or trimming is applied,
	/// and includes all layers and text.
	/// </summary>
	public Vector2 TopLeftEdge
	{
		get { return topLeftEdge; }
	}

	/// <summary>
	/// The bottom-right edge of the control, when
	/// no clipping or trimming is applied,
	/// and includes all layers and text.
	/// </summary>
	public Vector2 BottomRightEdge
	{
		get { return bottomRightEdge; }
	}



	//---------------------------------------------------
	// IUIObject interface stuff
	//---------------------------------------------------
	protected bool m_controlIsEnabled = true;

	/// <summary>
	/// Controls whether this control is in an enabled
	/// state or not. If it is not, input is not processed.
	/// This can also be used to cause a control to take on
	/// a "grayed out" appearance when disabled.
	/// </summary>
	public virtual bool controlIsEnabled
	{
		get { return m_controlIsEnabled; }
		set	{ m_controlIsEnabled = value; }
	}

	/// <summary>
	/// When set to true, the control will instruct any
	/// pointers which have it as their target to
	/// de-target them.  Use this if you are deactivating
	/// a control and want no input to go to it.
	/// It is strongly recommended NOT to use this feature
	/// on any control that appears in a scroll list, or
	/// else you may be unable to scroll past the edge of
	/// the list's viewable area.
	/// </summary>
	public virtual bool DetargetOnDisable
	{
		get { return DetargetOnDisable; }
		set { DetargetOnDisable = value; }
	}

	// Allows an object to act as a proxy for other
	// controls - i.e. a UIVirtualScreen
	// But in our case, just return ourselves since
	// we're not acting as a proxy
	public IUIObject GetControl(ref POINTER_INFO ptr)
	{
		return this;
	}

	protected IUIContainer container;

	public virtual IUIContainer Container
	{
		get { return container; }
		set 
		{ 
			if(container != null)
			{
				if (aggregateLayers != null)
				{
					for (int i = 0; i < aggregateLayers.Length; ++i)
					{
						if (aggregateLayers[i] != null)
						{
							for (int j = 0; j < aggregateLayers[i].Length; ++j)
								container.RemoveChild(aggregateLayers[i][j].gameObject);
						}
					}
				}

				if (spriteText != null)
					container.RemoveChild(spriteText.gameObject);
			}

			if (value != null)
			{
				if (aggregateLayers != null)
				{
					for (int i = 0; i < aggregateLayers.Length; ++i)
					{
						if (aggregateLayers[i] != null)
						{
							for (int j = 0; j < aggregateLayers[i].Length; ++j)
								value.AddChild(aggregateLayers[i][j].gameObject);
						}
					}
				}

				if (spriteText != null)
					value.AddChild(spriteText.gameObject);
			}

			container = value;
		}
	}

	public bool RequestContainership(IUIContainer cont)
	{
		Transform t = transform.parent;
		Transform c = ((Component)cont).transform;

		while(t != null)
		{
			if(t == c)
			{
				Container = cont;
				return true;
			}
			else if (t.gameObject.GetComponent("IUIContainer") != null)
				return false;

			t = t.parent;
		}

		// Never found *any* containers:
		return false;
	}

	public virtual bool GotFocus()	{ return false; }

	protected EZInputDelegate inputDelegate;
	protected EZValueChangedDelegate changeDelegate;
	
	// LiHaojie 2012.09.11 Add double click delegate.
	protected EZValueChangedDelegate dbClickDelegate;

	/// <summary>
	/// Sets the method to be called when input occurs (input is forwarded from OnInput()).
	/// NOTE: This will replace any and all delegates which have been set or added previously.  If you are unsure
	/// if any delegates are already registered, use AddInputDelegate() instead, or RemoveInputDelegate() to unset
	/// a previously registered delegate.  Only use this when you are sure you want to replace all previously registered delegates.
	/// </summary>
	/// <param name="del">A method that conforms to the EZInputDelegate pattern.</param>
	public virtual void SetInputDelegate(EZInputDelegate del)
	{
		inputDelegate = del;
	}

	/// <summary>
	/// Adds a method to be called when input occurs (input is forwarded from OnInput()).
	/// </summary>
	/// <param name="del">A method that conforms to the EZInputDelegate pattern.</param>
	public virtual void AddInputDelegate(EZInputDelegate del)
	{
		inputDelegate += del;
	}

	/// <summary>
	/// Removes a method added with AddInputDelegate().
	/// </summary>
	/// <param name="del">A method that conforms to the EZInputDelegate pattern.</param>
	public virtual void RemoveInputDelegate(EZInputDelegate del)
	{
		inputDelegate -= del;
	}

	/// <summary>
	/// Sets the method to be called when the value of a control changes (such as a checkbox changing from false to true, or a slider being moved).
	/// NOTE: This will replace any and all delegates which have been set or added previously.  If you are unsure
	/// if any delegates are already registered, use AddValueChangedDelegate() instead, or RemoveValueChangedDelegate() to unset
	/// a previously registered delegate.  Only use this when you are sure you want to replace all previously registered delegates.
	/// </summary>
	/// <param name="del">A method that conforms to the EZValueChangedDelegate pattern.</param>
	public virtual void SetValueChangedDelegate(EZValueChangedDelegate del)
	{
		changeDelegate = del;
	}

	/// <summary>
	/// Adds a method to be called when the value of a control changes (such as a checkbox changing from false to true, or a slider being moved).
	/// </summary>
	/// <param name="del">A method that conforms to the EZValueChangedDelegate pattern.</param>
	public virtual void AddValueChangedDelegate(EZValueChangedDelegate del)
	{
		changeDelegate += del;
	}

	/// <summary>
	/// Removes a method added with AddValueChangedDelegate().
	/// </summary>
	/// <param name="del">A method that conforms to the EZValueChangedDelegate pattern.</param>
	public virtual void RemoveValueChangedDelegate(EZValueChangedDelegate del)
	{
		changeDelegate -= del;
	}
	
	// LiHaojie 2012.09.11 Add double click event
	public virtual void SetDoubleClickDelegate(EZValueChangedDelegate del)
	{
		dbClickDelegate = del;
	}
	
	public virtual void AddDoubleClickDelegate(EZValueChangedDelegate del)
	{
		dbClickDelegate += del;
	}
	
	public virtual void RemoveDoubleClickDelegate(EZValueChangedDelegate del)
	{
		dbClickDelegate -= del;
	}
	
	// hxl add
	public bool isCtrlPressDur = true;
	float pressTime = 0;
	public virtual void Update()
	{
		if(pressTime > 0)
		{
			pressTime -= Time.deltaTime;
		}
	}

	/// <summary>
	/// This is where input handling code should go in any derived class.
	/// </summary>
	/// <param name="ptr">POINTER_INFO struct that contains information on the pointer that caused the event, as well as the event that occurred.</param>
	public virtual void OnInput(POINTER_INFO ptr)
	{
		if(pressTime >0 && isCtrlPressDur)
		{
			return;
		}
		
		if(ptr.evt == POINTER_INFO.INPUT_EVENT.TAP)
		{
			pressTime = 0.5f;
		}
		
		if(mouseTouchListeners != null)
		{
			mouseTouchListeners(ptr);
		}
		OnInput(ref ptr);
	}

	/// <summary>
	/// This is where input handling code should go in any derived class.
	/// </summary>
	/// <param name="ptr">POINTER_INFO struct that contains information on the pointer that caused the event, as well as the event that occurred.</param>
	public virtual void OnInput(ref POINTER_INFO ptr) 
	{
		if(Container != null)
		{
			ptr.callerIsControl = true;
			Container.OnInput(ptr);
		}
	}
	
	// Delegate definition for receiving pointer info
	public delegate void PointerInfoDelegate(POINTER_INFO ptr);
	protected static PointerInfoDelegate mouseTouchListeners; // Delegate to call with all mouse/touchpad input
	
	/// <summary>
	/// Registers a delegate to be called with all mouse 
	/// and touchpad pointer input.  Use this when you 
	/// want to "listen" to all mouse or touchpad input.
	/// </summary>
	/// <param name="del">Delegate to be called.</param>
	public static void AddMouseTouchPtrListener(PointerInfoDelegate del)
	{
		mouseTouchListeners += del;
	}
	
	/// <summary>
	/// Removes a mouse/touchpad pointer listener.
	/// </summary>
	/// <param name="del">Delegate to be removed.</param>
	public static void RemoveMouseTouchPtrListener(PointerInfoDelegate del)
	{
		mouseTouchListeners -= del;
	}

	#region Drag&Drop

	//---------------------------------------------------
	// Drag & Drop stuff
	//---------------------------------------------------
	
	// Encapsulates most of our drag-and-drop logic:
	protected EZDragDropHelper dragDropHelper = new EZDragDropHelper();

	/// <summary>
	/// Indicates whether the object can be dragged as part of a drag & drop operation.
	/// </summary>
	public bool isDraggable = false;

	/// <summary>
	/// A mask which can be used to make the object only be "droppable" on
	/// objects in a certain layer.
	/// NOTE: This mask is combined with the camera's mask.
	/// </summary>
	public LayerMask dropMask = -1;

	/// <summary>
	/// A mask which can be used to make the object only be "droppable" on
	/// objects in a certain layer.
	/// NOTE: This mask is combined with the camera's mask.
	/// </summary>
	public LayerMask DropMask
	{
		get { return dropMask; }
		set { dropMask = value; }
	}

	/// <summary>
	/// Indicates whether the object can be dragged as part of a drag & drop operation.
	/// </summary>
	public bool IsDraggable
	{
		get { return isDraggable; }
		set { isDraggable = value; }
	}

	/// <summary>
	/// The distance an object being dragged and dropped
	/// should be offset toward the camera to ensure it
	/// hovers above other objects and controls in the
	/// scene.
	/// A value of NaN indicates the default value will
	/// be used from the UIManager.
	/// </summary>
	public float dragOffset = float.NaN;

	/// <summary>
	/// The distance an object being dragged and dropped
	/// should be offset toward the camera to ensure it
	/// hovers above other objects and controls in the
	/// scene.
	/// </summary>
	public float DragOffset
	{
		get { return dragOffset; }
		set 
		{ 
			dragOffset = value;

			if(IsDragging)
			{
				POINTER_INFO ptr;
				if(UIManager.Exists())
				{
					if (UIManager.instance.GetPointer(this, out ptr))
					{
						dragDropHelper.DragUpdatePosition(ptr);
					}
				}
			}
		}
	}

	/// <summary>
	/// The type of easing to use to animate the object back to its starting
	/// position when a drag operation is canceled.
	/// </summary>
	public EZAnimation.EASING_TYPE cancelDragEasing = EZAnimation.EASING_TYPE.Default;

	/// <summary>
	/// The type of easing to use to animate the object back to its starting
	/// position when a drag operation is canceled.
	/// </summary>
	public EZAnimation.EASING_TYPE CancelDragEasing
	{
		get { return cancelDragEasing; }
		set { cancelDragEasing = value; }
	}

	/// <summary>
	/// The duration of the easing animation when a drag and drop operation
	/// is canceled.  A value of -1 indicates to use the default value
	/// specified in the UIManager.
	/// </summary>
	public float cancelDragDuration = -1f;

	/// <summary>
	/// The duration of the easing animation when a drag and drop operation
	/// is canceled.
	/// </summary>
	public float CancelDragDuration
	{
		get { return cancelDragDuration; }
		set { cancelDragDuration = value; }
	}

	/// <summary>
	/// Indicates whether the object is being dragged as part of a drag & drop operation.
	/// Setting this value to false while the object is being dragged will cause the drag
	/// and drop operation to be canceled.
	/// </summary>
	public bool IsDragging
	{
		get { return dragDropHelper.IsDragging; }
		set { dragDropHelper.IsDragging = value; }
	}

	/// <summary>
	/// The GameObject over which the object being dragged
	/// is hovering and will attempt to be dropped if it 
	/// let go.
	/// </summary>
	public GameObject DropTarget
	{
		get { return dragDropHelper.DropTarget; }
		set { dragDropHelper.DropTarget = value; }
	}

	/// <summary>
	/// In the context of a drag & drop operation, this
	/// indicates whether the drop action was handled.
	/// If this is not set to true in response to a
	/// Dropped message sent to OnEZDragDrop(), the
	/// drop will be considered to be unhandled and will
	/// result in a canceled drop, causing the dragged
	/// object to return to its original position.
	/// </summary>
	public bool DropHandled
	{
		get { return dragDropHelper.DropHandled; }
		set { dragDropHelper.DropHandled = value; }
	}

	// Updates the position of an object being dragged and dropped
	// according to the current pointer position.
	// This is called internally by UIManager's DoDragUpdate().
	public void DragUpdatePosition(POINTER_INFO ptr)
	{
		dragDropHelper.DragUpdatePosition(ptr);
	}

	/// <summary>
	/// The default method of updating the drag position
	/// </summary>
	/// <param name="ptr">The pointer info struct for the pointer dragging the object.</param>
	public void DefaultDragUpdatePosition(POINTER_INFO ptr)
	{
		dragDropHelper.DefaultDragUpdatePosition(ptr);
	}

	/// <summary>
	/// Sets the delegate to be called in order to update the drag
	/// position of the object being dragged.
	/// </summary>
	/// <param name="del">The delegate that will update the object's position.</param>
	public void SetDragPosUpdater(EZDragDropHelper.UpdateDragPositionDelegate del)
	{
		dragDropHelper.SetDragPosUpdater(del);
	}

	/// <summary>
	/// Cancels any pending drag and drop operation.
	/// </summary>
	public void CancelDrag()
	{
		dragDropHelper.CancelDrag();
	}

	/// <summary>
	/// Indicates whether to use the default drag canceling animation.
	/// NOTE: If you perform your own cancel animation, be sure to call
	/// CancelFinished() upon the animation's completion.
	/// </summary>
	public bool UseDefaultCancelDragAnim
	{
		get { return dragDropHelper.UseDefaultCancelDragAnim; }
		set
		{
			dragDropHelper.UseDefaultCancelDragAnim = value;
		}
	}

	/// <summary>
	/// Signals to the object that its cancel drag transition
	/// has completed.  Only call this yourself if you have
	/// overridden the default drag canceling and you are
	/// finished animating/whatever the object as a result of
	/// having its drag canceled.
	/// </summary>
	public void CancelFinished()
	{
		dragDropHelper.CancelFinished();
	}

	/// <summary>
	/// Performs the default cancel drag animation.
	/// </summary>
	public void DoDefaultCancelDrag()
	{
		dragDropHelper.DoDefaultCancelDrag();
	}

	// <summary>
	// Receives regular notification of drag & drop events
	// pertaining to this object when an object is being
	// dragged.  This is called on potential drop targets
	// when an object is dragged over them.  It is also
	// called on the object(s) being dragged/dropped.
	// </summary>
	// <param name="parms">The EZDragDropParams structure which holds information about the event.</param>
	public void OnEZDragDrop_Internal(EZDragDropParams parms)
	{
		dragDropHelper.OnEZDragDrop_Internal(parms);
	}

	/// <summary>
	/// Adds a delegate to be called with drag and drop notifications.
	/// </summary>
	/// <param name="del">The delegate to add.</param>
	public void AddDragDropDelegate(EZDragDropDelegate del)
	{
		dragDropHelper.AddDragDropDelegate(del);
	}

	/// <summary>
	/// Removes a delegate from the list of those to be called 
	/// with drag and drop notifications.
	/// </summary>
	/// <param name="del">The delegate to add.</param>
	public void RemoveDragDropDelegate(EZDragDropDelegate del)
	{
		dragDropHelper.RemoveDragDropDelegate(del);
	}

	/// <summary>
	/// Sets the delegate to be called with drag and drop notifications.
	/// NOTE: This will replace any previously registered delegates.
	/// </summary>
	/// <param name="del">The delegate to add.</param>
	public void SetDragDropDelegate(EZDragDropDelegate del)
	{
		dragDropHelper.SetDragDropDelegate(del);
	}

	// Setters for the internal drag drop handler delegate:
	public void SetDragDropInternalDelegate(EZDragDropHelper.DragDrop_InternalDelegate del)
	{
		dragDropHelper.SetDragDropInternalDelegate(del);
	}
	public void AddDragDropInternalDelegate(EZDragDropHelper.DragDrop_InternalDelegate del)
	{
		dragDropHelper.AddDragDropInternalDelegate(del);
	}
	public void RemoveDragDropInternalDelegate(EZDragDropHelper.DragDrop_InternalDelegate del)
	{
		dragDropHelper.RemoveDragDropInternalDelegate(del);
	}
	public EZDragDropHelper.DragDrop_InternalDelegate GetDragDropInternalDelegate()
	{
		return dragDropHelper.GetDragDropInternalDelegate();
	}


	#endregion


	//---------------------------------------------------
	// Inspector/Editor stuff
	//---------------------------------------------------

	// Draws the UI for the control's
	// properties before state selection.
	// Accepts the currently-selected state
	// index.
	// The height of the content drawn is
	// returned so that other UI elements 
	// can be adjusted accordingly:
	public virtual int DrawPreStateSelectGUI(int selState, bool inspector) { return 0; }

	// Draws the UI for the control's
	// properties after state selection.
	// Accepts the currently-selected state
	// index.
	// The height of the content drawn is
	// returned so that other UI elements 
	// can be adjusted accordingly:
	public virtual int DrawPostStateSelectGUI(int selState) { return 0; }

	// Draws the UI for the control's properties
	// just before the transition UI stuff is drawn.
	public virtual void DrawPreTransitionUI(int selState, IGUIScriptSelector gui) {}

	// Returns an array of strings which are the
	// names of the states/elements supported by
	// the control.
	public virtual string[] EnumStateElements()
	{
		string[] names = new string[States.Length];

		for (int i = 0; i < States.Length; ++i)
			names[i] = States[i].name;

		return names;
	}

	// So that we can access each control type's different
	// transition arrays in a generic manner:
	public virtual EZTransitionList GetTransitions(int index) { return null; }

	public abstract EZTransitionList[] Transitions
	{
		get;
		set;
	}

	/// <summary>
	/// Gets/sets the array of strings that correspond to each
	/// control state.
	/// </summary>
	public virtual string GetStateLabel(int index)
	{
		return null;
	}

	/// <summary>
	/// Sets the string for the specified state label.
	/// </summary>
	/// <param name="index">index of the state to set.</param>
	/// <param name="label">The string to set as the state's label.</param>
	public virtual void SetStateLabel(int index, string label)
	{}


	// Returns the info for the specified state/element.
	public virtual ASCSEInfo GetStateElementInfo(int stateNum)
	{
		ASCSEInfo info = new ASCSEInfo();

		info.stateObj = States[stateNum];
		info.transitions = GetTransitions(stateNum);
		info.stateLabel = GetStateLabel(stateNum);

		return info;
	}

	
	// Sets the label text of the control to 
	// the state label of the specified state.
	protected void UseStateLabel(int index)
	{
		string label = GetStateLabel(index);
		
		// If we want to keep whatever we've
		// already got, bail:
		if (label == DittoString)
			return;

		// If we don't have any text to show
		// and don't have a SpriteText anyway,
		// just bail:
		if (label == "" && spriteText == null)
			return;

		Text = label;
	}


	// Sets the state element info of the control
	// (texture and transition).
	//public abstract void SetStateElementInfo(int stateNum, ASCSEInfo info);


	//---------------------------------------------------
	// Edit-time updating stuff
	//---------------------------------------------------

	// Ensures that the control is updated in the scene view
	// while editing:
	public override void DoMirror()
	{
		// Only run if we're not playing:
		if (Application.isPlaying)
			return;

		// This means Awake() was recently called, meaning
		// we couldn't reliably get valid camera viewport
		// sizes, so we zeroed them out so we'd know to
		// get good values later on (when OnDrawGizmos()
		// is called):
		if (screenSize.x == 0 || screenSize.y == 0)
			Start();

		if (mirror == null)
		{
			mirror = new AutoSpriteControlBaseMirror();
			mirror.Mirror(this);
		}

		mirror.Validate(this);

		// Compare our mirrored settings to the current settings
		// to see if something was changed:
		if (mirror.DidChange(this))
		{
			Init();
			mirror.Mirror(this);	// Update the mirror
		}
	}
}


// Mirrors the editable settings of a control that affect
// how the control is drawn in the scene view
public class AutoSpriteControlBaseMirror : SpriteRootMirror
{
	string text;
	float textOffsetZ;

	// Mirrors the specified control's settings
	public override void Mirror(SpriteRoot s)
	{
		AutoSpriteControlBase c = (AutoSpriteControlBase)s;

		base.Mirror(s);
		text = c.text;
		textOffsetZ = c.textOffsetZ;
	}


	// Returns true if any of the settings do not match:
	public override bool DidChange(SpriteRoot s)
	{
		AutoSpriteControlBase c = (AutoSpriteControlBase)s;

		if (text != c.text)
		{
			c.Text = c.text;
			return true;
		}

		if (textOffsetZ != c.textOffsetZ)
		{
			if (c.spriteText != null)
				c.spriteText.offsetZ = textOffsetZ;
			return true;
		}

		return base.DidChange(s);
	}
}